package common;

import java.io.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;


/**
 * This class supports stream operations 
 * and is exploited frequently in this project.
 * For specific reasons it is much more convenient 
 * to implement application-specific methods rather 
 * than use java libraries. 
 */
public class ByteStream {

	/**
	 * Integer passed to a corresponding byte array
	 * @param in_int disposed integer
	 * @return corresponding byte array
	 */
	private static byte[] toByteArray(int in_int) {
		byte a[] = new byte[4];
		for (int i=0; i < 4; i++) {

			int  b_int = (in_int >> (i*8) ) & 255;
			byte b = (byte) ( b_int );

			a[i] = b;
		}
		return a;
	}

	/**
	 * Fuction receives byte array and parses it to integer
	 * @param byte_array_4 parsed array
	 * @return corresponding integer
	 */
	private static int toInt(byte[] byte_array_4) {
		int ret = 0;  
		for (int i=0; i<4; i++) {
			int b = (int) byte_array_4[i];
			if (i<3 && b<0) {
				b=256+b;
			}
			ret += b << (i*8);
		}
		return ret;
	}

	/**
	 * Function receives byte stream and parses it to a matching integer
	 * @param in byte stream
	 * @return corresponding integer
	 * @throws java.io.IOException
	 */
	public static int toInt(InputStream in) throws java.io.IOException {
		byte[] byte_array_4 = new byte[4];

		byte_array_4[0] = (byte) in.read();
		byte_array_4[1] = (byte) in.read();
		byte_array_4[2] = (byte) in.read();
		byte_array_4[3] = (byte) in.read();

		return toInt(byte_array_4);
	}

	/**
	 * Function receives input stream and parses it to appropriate string
	 * @param ins byte stream
	 * @return string
	 * @throws java.io.IOException
	 */
	public static String toString(InputStream ins) throws java.io.IOException {
		int len = toInt(ins);
		return toString(ins, len);
	}

	/**
	 * Function receives input stream and parses it to appropriate string with the character limit. 
	 * @param ins parsed input stream
	 * @param len character limit
	 * @return appropriate string
	 * @throws java.io.IOException
	 */
	private static String toString(InputStream ins, int len) throws java.io.IOException {
		String ret=new String();
		for (int i=0; i<len;i++) {
			ret+=(char) ins.read();
		}
		return ret;
	}

	/**
	 * Function writes an integer to specified byte output stream
	 * @param os specified output stream
	 * @param i integer put to stream
	 * @throws java.io.IOException
	 */
	public static void toStream(OutputStream os, int i) throws java.io.IOException {
		byte [] byte_array_4 = toByteArray(i);
		os.write(byte_array_4);
	}

	/**
	 * Function writes given string to specified byte output stream
	 * @param s string put to stream
	 * @param os pecified output stream
	 * @throws java.io.IOException
	 */
	public static void toStream(OutputStream os, String s) throws java.io.IOException {
		int len_s = s.length();
		toStream(os, len_s);
		for (int i=0;i<len_s;i++) {
			os.write((byte) s.charAt(i));
		}
		os.flush();
	}



	/**
	 * Function, used to parse bytes from stream into a file.
	 * @param ins initial stream
	 * @param fos output file stream
	 * @param len length delimiter
	 * @param buf_size size of a buffer used o parse stream
	 * @throws java.io.FileNotFoundException
	 * @throws java.io.IOException
	 */
	private static void toFile(InputStream ins, FileOutputStream fos, int len, int buf_size) throws 
	java.io.FileNotFoundException, 
	java.io.IOException {

		byte[] buffer = new byte[buf_size];

		int       len_read=0;
		int total_len_read=0;

		while ( total_len_read + buf_size <= len) {
			len_read = ins.read(buffer);
			total_len_read += len_read;
			fos.write(buffer, 0, len_read);
		}

		if (total_len_read < len) {
			toFile(ins, fos, len-total_len_read, buf_size/2);
		}
	}

	/**
	 * Function, used to write a stream into the file 
	 * using not more than specific number of bytes.
	 * @param ins specified input stream
	 * @param file file to write to
	 * @param len length delimiter
	 * @throws java.io.FileNotFoundException
	 * @throws java.io.IOException
	 */
	private static void toFile(InputStream ins, File file, int len) throws 
	java.io.FileNotFoundException, 
	java.io.IOException {
		System.out.println(file);
		FileOutputStream fos=new FileOutputStream(file);

		toFile(ins, fos, len, 1024);
	}
	/**
	 * Function, used to write a stream into the file 
	 * @param ins specified input stream
	 * @param file file to write to
	 * @throws java.io.FileNotFoundException
	 * @throws java.io.IOException
	 */
	public static void toFile(InputStream ins, File file) throws 
	java.io.FileNotFoundException, 
	java.io.IOException {

		int len = toInt(ins);
		toFile(ins, file, len);
	}

	/**
	 * Function, used to pass a file into a stream.
	 * @param os specific stream
	 * @param file disposed file
	 * @throws java.io.FileNotFoundException
	 * @throws java.io.IOException
	 */
	public static void toStream(OutputStream os, File file) 
			throws java.io.FileNotFoundException,
			java.io.IOException{

		toStream(os, (int) file.length());

		byte b[]=new byte[1024];
		InputStream is = new FileInputStream(file);
		int numRead=0;

		while ( ( numRead=is.read(b)) > 0) {
			os.write(b, 0, numRead);
		}
		os.flush();
	}
}